package com.cloud.adrian.novel.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.TotalHits;
import co.elastic.clients.json.JsonData;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.cloud.adrian.novel.book.dto.req.BookSearchReqDto;
import com.cloud.adrian.novel.book.dto.resp.BookEsRespDto;
import com.cloud.adrian.novel.book.dto.resp.BookInfoRespDto;
import com.cloud.adrian.novel.common.constant.EsConstants;
import com.cloud.adrian.novel.common.resp.PageRespDto;
import com.cloud.adrian.novel.common.resp.RestResp;
import com.cloud.adrian.novel.search.manager.feign.BookFeignManager;
import com.cloud.adrian.novel.search.service.SearchService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 搜索服务实现类
 * @Author: xiangguiliang
 * @version: 1.0
 * @since: 2024/05/26
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class SearchServiceImpl implements SearchService {

    private final ElasticsearchClient esClient;
    private final BookFeignManager bookFeignManager;

    @SneakyThrows
    @Override
    public RestResp<PageRespDto<BookInfoRespDto>> searchBooks(BookSearchReqDto condition) {
        SearchResponse<BookEsRespDto> response = esClient.search(s -> {
            SearchRequest.Builder searchBuilder = s.index(EsConstants.BookIndex.INDEX_NAME);
            // 构建搜索条件
            buildSearchCondition(condition, searchBuilder);
            // 排序
            if(!StringUtils.isBlank(condition.getSort())) {
                searchBuilder.sort(o -> o.field(
                                f -> f.field(StringUtils.underlineToCamel(condition.getSort().split(" ")[0]))
                                        .order(SortOrder.Desc)
                        ));
            }
            // 分页
            searchBuilder.from((condition.getPageNum() - 1) * condition.getPageSize())
                    .size(condition.getPageSize());
            // 设置高亮显示
            searchBuilder.highlight(
                    h -> h.fields(EsConstants.BookIndex.FIELD_BOOK_NAME,
                                    t -> t.preTags("<em style = 'color:red'>")
                                            .postTags("</em>"))
                            .fields(EsConstants.BookIndex.FIELD_AUTHOR_NAME,
                                    t -> t.preTags("<em style = 'color:red'>")
                                            .postTags("</em>")));
            return searchBuilder;
        }, BookEsRespDto.class);

        TotalHits total = response.hits().total();
        List<BookInfoRespDto> list = new ArrayList<>();

        List<Hit<BookEsRespDto>> hits = response.hits().hits();

        for(var hit : hits) {
            BookEsRespDto book = hit.source();
            assert book != null;
            if (!CollectionUtils.isEmpty(hit.highlight().get(EsConstants.BookIndex.FIELD_BOOK_NAME))) {
                book.setBookName(hit.highlight().get(EsConstants.BookIndex.FIELD_BOOK_NAME).get(0));
            }
            if (!CollectionUtils.isEmpty(hit.highlight().get(EsConstants.BookIndex.FIELD_AUTHOR_NAME))) {
                book.setAuthorName(hit.highlight().get(EsConstants.BookIndex.FIELD_AUTHOR_NAME).get(0));
            }
            list.add(BookInfoRespDto.builder()
                    .id(book.getId())
                    .bookName(book.getBookName())
                    .categoryId(book.getCategoryId())
                    .categoryName(book.getCategoryName())
                    .authorId(book.getAuthorId())
                    .authorName(book.getAuthorName())
                    .wordCount(book.getWordCount())
                    .lastChapterName(book.getLastChapterName())
                    .build());
        }

        assert total != null;
        return RestResp.ok(PageRespDto.of(condition.getPageNum(), condition.getPageSize(), total.value(), list));
    }

    /**
     * 构建搜索条件
     */
    private void buildSearchCondition(BookSearchReqDto condition, SearchRequest.Builder searchBuilder) {
        BoolQuery boolQuery = BoolQuery.of(b -> {
            // 只查询有字数的小说
            b.must(RangeQuery.of(
                    m -> m.field(EsConstants.BookIndex.FIELD_WORD_COUNT)
                          .gt(JsonData.of(0))
            )._toQuery());

            if (!StringUtils.isBlank(condition.getKeyword())) {
                // 关键词不为空，进行关键词匹配
                b.must((q -> q.multiMatch(
                        t -> t.fields(EsConstants.BookIndex.FIELD_BOOK_NAME + "^2",
                                EsConstants.BookIndex.FIELD_AUTHOR_NAME + "^1.8",
                                EsConstants.BookIndex.FIELD_BOOK_DESC + "^0.1")
                                .query(condition.getKeyword()))
                ));
            }

            if (Objects.nonNull(condition.getWorkDirection())) {
                // 作评频道精确查询
                b.must(TermQuery.of(
                        m -> m.field(EsConstants.BookIndex.FIELD_WORK_DIRECTION)
                                .value(condition.getWorkDirection())
                )._toQuery());
            }

            if (Objects.nonNull(condition.getCategoryId())) {
                // 类别id精确查询
                b.must(TermQuery.of(
                        m -> m.field(EsConstants.BookIndex.FIELD_CATEGORY_ID)
                                .value(condition.getCategoryId())
                )._toQuery());
            }

            if (Objects.nonNull(condition.getBookStatus())) {
                b.must(TermQuery.of(
                        m -> m.field(EsConstants.BookIndex.FIELD_BOOK_STATUS)
                                .value(condition.getBookStatus())
                )._toQuery());
            }

            // 字数大于或等于最小值
            if (Objects.nonNull(condition.getWordCountMin())) {
                b.must(RangeQuery.of(
                        m -> m.field(EsConstants.BookIndex.FIELD_WORD_COUNT)
                                .gte(JsonData.of(condition.getWordCountMin()))
                )._toQuery());
            }

            // 字数小于最大值
            if (Objects.nonNull(condition.getWordCountMax())) {
                b.must(RangeQuery.of(
                        m -> m.field(EsConstants.BookIndex.FIELD_WORD_COUNT)
                                .lt(JsonData.of(condition.getWordCountMax()))
                )._toQuery());
            }

            // 时间范围查询
            if (Objects.nonNull(condition.getUpdateTimeMin())) {
                b.must(RangeQuery.of(
                        m -> m.field(EsConstants.BookIndex.FIELD_LAST_CHAPTER_UPDATE_TIME)
                                .gte(JsonData.of(condition.getUpdateTimeMin()))
                )._toQuery());

            }
            return b;
        });

        searchBuilder.query(q -> q.bool(boolQuery));
    }

    @Override
    public void insertNovel(Long bookId) throws IOException {
        BookEsRespDto bookEsRespDto = bookFeignManager.selectBookById(bookId);
        esClient.create(
                e -> e.index(EsConstants.BookIndex.INDEX_NAME).id(bookId.toString()).document(bookEsRespDto)
        );
    }

    @Override
    public void updateNovel(Long bookId) throws IOException {
        // 获取当前小说信息
        BookEsRespDto bookEsRespDto = bookFeignManager.selectBookById(bookId);
        // 更新文档
        esClient.update(
                e -> e.index(EsConstants.BookIndex.INDEX_NAME).id(bookId.toString())
                        .doc(bookEsRespDto), BookEsRespDto.class
        );
    }

    @Override
    public void deleteNovel(Long bookId) throws IOException {
        esClient.delete(
                e -> e.index(EsConstants.BookIndex.INDEX_NAME).id(bookId.toString())
        );
    }
}
